using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;

using Microsoft.StyleCop;
using Microsoft.VisualStudio.TestTools.UnitTesting;

using StyleCopContrib.Runner;

namespace StyleCopContrib.CustomRules.Tests
{
	public class RuleTestBase : IDisposable
	{
		#region Fields

		private readonly string _projectRootPath;
		private readonly IList<Expectation> _expectations;
		private ConsoleRunner _consoleRunner;
		private string _codeFile;
		private AnalysisResults _analysisResults;
		private SettingsManager _settingsManager;

		private bool _isDisposed;
		private string _projectSettingsPath;

		#endregion

		#region Constructors

		public RuleTestBase()
		{
			this._projectRootPath =
				Path.GetDirectoryName(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));

			this._expectations = new List<Expectation>();
		}

		#endregion

		#region Methods

		[TestInitialize]
		public void Setup()
		{
			this._expectations.Clear();
			this._codeFile = null;
			this._analysisResults = null;

			this._settingsManager = ServiceLocator.GetService<SettingsManager>();
			if (this._settingsManager == null)
			{
				this._settingsManager = new SettingsManager();
				ServiceLocator.RegisterService<SettingsManager>(this._settingsManager);
			}

			this._settingsManager.ResetSettings();

			this.TestSetup();
		}

		protected virtual void TestSetup()
		{
		}

		protected void SetTargetFile(string relativeFilePathFromProjectRoot)
		{
			this._codeFile = Path.Combine(this._projectRootPath, relativeFilePathFromProjectRoot);
		}

		protected void SetBaseProjectSettingFile(string relativeFilePathFromProjectRoot)
		{
			this._projectSettingsPath = Path.Combine(this._projectRootPath, relativeFilePathFromProjectRoot);
		}

		protected void SetAnalyzerSetting<T>(string propertyName, T propertyValue)
		{
			this._settingsManager.SetSetting(propertyName, propertyValue);
		}

		protected void Analyze()
		{
			if (this._codeFile == null) throw new InvalidOperationException("TargetFile must be set before analysis");

			string settingsPath = this.GetSettingsFilePath();

			this._consoleRunner = new ConsoleRunner(settingsPath, null);

			CodeProject codeProject = ProjectUtility.CreateOneFileProject(this._codeFile, this._consoleRunner.Environment);

			this._analysisResults = this._consoleRunner.Analyze(codeProject);
		}

		protected void AddExpectation(Enum violatedRule, params int[] lineNumbers)
		{
			foreach (int lineNumber in lineNumbers)
			{
				this._expectations.Add(new Expectation { ViolatedRule = violatedRule, LineNumber = lineNumber });
			}
		}

		protected void ValidateExpectations()
		{
			IList<Violation> actualViolations = new List<Violation>(this._analysisResults.Violations);
			IList<Expectation> unmatchedExpectations = new List<Expectation>();

			foreach (Expectation expectation in this._expectations)
			{
				Violation matchingViolation = null;

				foreach (Violation violation in actualViolations)
				{
					if ((expectation.ViolatedRule.ToString() == violation.Rule.Name) &&
						(expectation.LineNumber == violation.Line))
					{
						matchingViolation = violation;
						break;
					}
				}

				if (matchingViolation != null)
				{
					actualViolations.Remove(matchingViolation);
				}
				else
				{
					unmatchedExpectations.Add(expectation);
				}
			}

			RuleTestBase.ReportUnmatchedViolations(unmatchedExpectations, actualViolations);
		}

		private string GetSettingsFilePath()
		{
			return this._projectSettingsPath;
		}

		private static void ReportUnmatchedViolations(ICollection<Expectation> unmatchedExpectations,
													  ICollection<Violation> unmatchedViolations)
		{
			StringBuilder message = new StringBuilder();

			if (unmatchedExpectations.Count > 0)
			{
				message.AppendLine("The following expectations where not found in the code file");
				foreach (Expectation expectation in unmatchedExpectations)
				{
					message.AppendLine(expectation.ViolatedRule + " at line " + expectation.LineNumber);
				}
			}

			if (unmatchedViolations.Count > 0)
			{
				message.AppendLine("The following violations where not expected in the code file");
				foreach (Violation violation in unmatchedViolations)
				{
					message.AppendLine(violation.Rule.Name + " at line " + violation.Line);
				}
			}

			if (message.Length > 0) Assert.Fail(message.ToString());
		}

		private void Dispose(bool isDisposing)
		{
			if (!this._isDisposed)
			{
				if (isDisposing)
				{
					if (this._consoleRunner != null)
					{
						this._consoleRunner.Dispose();
					}
				}

				this._isDisposed = true;
			}
		}

		#endregion

		#region IDispose

		public void Dispose()
		{
			this.Dispose(true);
			GC.SuppressFinalize(this);
		}

		#endregion

		#region Nested Types

		private sealed class Expectation
		{
			public Enum ViolatedRule { get; set; }

			public int LineNumber { get; set; }
		}

		#endregion
	}
}